package cn.ultrarisk.sprinx.mybatis.dao;

import cn.ultrarisk.sprinx.mybatis.annotation.Table;
import cn.ultrarisk.sprinx.mybatis.config.MybatisContext;
import cn.ultrarisk.sprinx.domain.Entity;
import cn.ultrarisk.sprinx.domain.Page;
import cn.ultrarisk.sprinx.mybatis.exception.MybatisDAOCheckedException;
import cn.ultrarisk.sprinx.mybatis.mapper.MapperLoader;
import cn.ultrarisk.sprinx.mybatis.mapper.MapperStatement;
import cn.ultrarisk.sprinx.mybatis.query.Column;
import cn.ultrarisk.sprinx.mybatis.query.Column.Fx;
import cn.ultrarisk.sprinx.mybatis.query.Query;
import cn.ultrarisk.sprinx.mybatis.query.Updater;
import cn.ultrarisk.sprinx.util.Utilities;
import org.apache.commons.beanutils.PropertyUtils;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

import java.util.*;

/**
 * 通用DAO
 *
 * @author sevendlong(lin23871@163.com)
 * @date 2014/4/14
 * @since V1.0
 */
public abstract class MybatisDAO<T, P> implements ApplicationContextAware {
    protected ApplicationContext applicationContext;
    protected SqlSessionTemplate sqlSessionTemplate;

    /**
     * 字段属性映射表
     */
    protected Map<String, String> columnPropertyMap;
    /**
     * 属性字段映射表
     */
    protected Map<String, String> propertyColumnMap;
    /**
     * 实体名
     */
    protected String entityName;
    /**
     * 实体类型
     */
    protected Class<T> entityClass;
    /**
     * 实体全部属性
     */
    protected String[] entityProperties;
    /**
     * 实体主键
     */
    protected String idProperty;
    /**
     * 表名
     */
    protected String tableName;
    /**
     * 表全部字段
     */
    protected String[] tableColumns;
    /**
     * 主键是否自增长
     */
    protected boolean autoIncrement;
    /**
     * 表主键
     */
    protected String idColumn;

    /**
     * 默认的mybatis查询语句
     */
    protected MapperStatement statement;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
        this.sqlSessionTemplate = applicationContext.getBean(SqlSessionTemplate.class);

        //构建通用查询语句
        this.statement = new MapperStatement(this.entityName);

        //载入mybatis配置文件
        String mapperResource = buildMapperResourcePath();
        MapperLoader<T> mybatisLoader = new MapperLoader<T>(this.sqlSessionTemplate.getConfiguration(), entityClass, entityName,
                autoIncrement, columnPropertyMap, idProperty, idColumn);
        mybatisLoader.loadAlias();
        mybatisLoader.loadResultMap();
        mybatisLoader.loadBaseSqlFragment(mapperResource);
        mybatisLoader.loadEntityMapper(mapperResource);
        mybatisLoader.loadExtendStatement(mapperResource);
    }

    /**
     * 构造器
     *
     * @param entityClass       实体类型
     * @param columnPropertyMap 字段属性映射表
     */
    public MybatisDAO(Class<T> entityClass, Map<String, String> columnPropertyMap) {
        this.entityClass = entityClass;
        this.entityName = entityClass.getSimpleName();

        this.columnPropertyMap = columnPropertyMap;
        this.propertyColumnMap = new HashMap<String, String>();
        for (String column : this.columnPropertyMap.keySet()) {
            String property = this.columnPropertyMap.get(column);
            this.propertyColumnMap.put(property, column);
        }
        this.tableColumns = Utilities.toArray(columnPropertyMap.keySet());
        this.entityProperties = Utilities.toArray(columnPropertyMap.values());

        Table table = entityClass.getAnnotation(Table.class);
        this.tableName = table.table();
        this.autoIncrement = table.autoIncrement();
        this.idColumn = table.idColumn();
        this.idProperty = columnPropertyMap.get(this.idColumn);
    }

    /**
     * 获取Mybatis的mapper路径
     *
     * @return 返回mapper路径
     */
    protected abstract String buildMapperResourcePath();

    /**
     * 获取实体对象的属性值
     *
     * @param entity   实体对象
     * @param property 属性名
     * @param <E>      属性类型
     * @return 返回属性值
     */
    @SuppressWarnings("unchecked")
    private <E> E getEntityPropertyValue(T entity, String property) {
        try {
            return (E) PropertyUtils.getProperty(entity, property);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 根据id判断对象是否存在
     *
     * @param id 主键编号
     * @return 返回对象
     */
    public boolean exist(P id) {
        return get(id, idColumn) != null;
    }

    /**
     * 根据查询参数判断对象是否存在
     *
     * @param query 查询参数
     * @return 返回对象
     */
    public boolean exist(Query query) {
        return get(query, idColumn) != null;
    }

    /**
     * 根据id获取指定字段的对象
     *
     * @param id           主键编号
     * @param tableColumns 指定表的字段
     * @return 返回对象
     */
    public T get(P id, String... tableColumns) {
        if (id == null) {
            throw new MybatisDAOCheckedException("id can't be null");
        }
        return get(Query.query(this.idProperty, id), tableColumns);
    }

    /**
     * 根据id获取指定字段的Map对象
     *
     * @param id           主键编号
     * @param tableColumns 指定表的字段
     * @return 返回Map对象
     */
    public Entity getEntity(P id, String... tableColumns) {
        if (id == null) {
            throw new MybatisDAOCheckedException("id can't be null");
        }
        return getEntity(Query.query(this.idProperty, id), tableColumns);
    }

    /**
     * 根据查询参数获取对象
     *
     * @param query        查询参数
     * @param tableColumns 指定表的字段
     * @return 返回Map对象
     */
    public T get(Query query, String... tableColumns) {
        List<T> list = findList(query, tableColumns);
        return (list != null && list.size() > 0) ? list.get(0) : null;
    }

    /**
     * 根据查询参数获取Map对象
     *
     * @param query        查询参数
     * @param tableColumns 指定表的字段
     * @return 返回对象
     */
    public Entity getEntity(Query query, String... tableColumns) {
        List<Entity> list = findEntityList(query, tableColumns);
        return (list != null && list.size() > 0) ? list.get(0) : null;
    }

    /**
     * 根据ids查询对象列表
     *
     * @param ids          主键编号
     * @param tableColumns 指定表的字段
     * @return 返回对象列表
     */
    public List<T> findByIds(Collection<P> ids, String... tableColumns) {
        if (ids == null || ids.size() == 0) {
            throw new MybatisDAOCheckedException("ids can't be empty");
        }
        return findList(Query.inQuery(idColumn, ids), tableColumns);
    }

    /**
     * 根据ids查询Map对象列表
     *
     * @param ids          主键编号
     * @param tableColumns 指定表的字段
     * @return 返回Map对象列表
     */
    public List<Entity> findEntityByIds(Collection<P> ids, String... tableColumns) {
        if (ids == null || ids.size() == 0) {
            throw new MybatisDAOCheckedException("ids can't be empty");
        }
        return findEntityList(Query.inQuery(idColumn, ids), tableColumns);
    }

    /**
     * 根据参数查询前N条对象列表
     *
     * @param query        查询参数
     * @param top          返回数量
     * @param tableColumns 指定表的字段
     * @return 返回对象列表
     */
    public List<T> findTop(Query query, int top, String... tableColumns) {
        return findList(query.start(0).limit(top), tableColumns);
    }

    /**
     * 根据参数查询前N条Map对象列表
     *
     * @param query        查询参数
     * @param top          返回数量
     * @param tableColumns 指定表的字段
     * @return 返回Map对象列表
     */
    public List<Entity> findEntityTop(Query query, int top, String... tableColumns) {
        return findEntityList(query.start(0).limit(top), tableColumns);
    }

    /**
     * 根据参数分页查询对象列表
     *
     * @param query        查询参数
     * @param pageNo       当前页
     * @param pageSize     每页记录数
     * @param tableColumns 指定表的字段
     * @return 返回对象列表
     */
    public Page<T> findPage(Query query, int pageNo, int pageSize, String... tableColumns) {
        query.start(((pageNo - 1) * pageSize)).limit(pageSize);
        List<T> list = findList(query, tableColumns);
        long count = count(query);
        return new Page<T>(count, pageNo, pageSize, list);
    }

    /**
     * 根据参数分页查询Map对象列表
     *
     * @param query        查询参数
     * @param pageNo       当前页
     * @param pageSize     每页记录数
     * @param tableColumns 指定表的字段
     * @return 返回Map对象列表
     */
    public Page<Entity> findEntityPage(Query query, int pageNo, int pageSize, String... tableColumns) {
        query.start(((pageNo - 1) * pageSize)).limit(pageSize);
        List<Entity> list = findEntityList(query, tableColumns);
        long count = count(query);
        return new Page<Entity>(count, pageNo, pageSize, list);
    }

    /**
     * 根据参数查询对象列表
     *
     * @param query        查询参数
     * @param tableColumns 指定表的字段
     * @return 返回对象列表
     */
    public List<T> findList(Query query, String... tableColumns) {
        if (tableColumns == null || tableColumns.length == 0) {
            tableColumns = this.tableColumns;
        }
        return sqlSessionTemplate.selectList(statement.listStatement(), query.toMap(this.tableName, Utilities.asList(tableColumns)));
    }

    /**
     * 根据参数查询Map对象列表
     *
     * @param query        查询参数
     * @param tableColumns 指定表的字段
     * @return 返回Map对象列表
     */
    public List<Entity> findEntityList(Query query, String... tableColumns) {
        if (tableColumns == null || tableColumns.length == 0) {
            tableColumns = this.tableColumns;
        }
        List<Map<String, Object>> list = sqlSessionTemplate.selectList(statement.listMapStatement(), query.toMap(this.tableName, Utilities.asList(tableColumns)));
        return Entity.cast(list);
    }

    /**
     * 根据参数和mapKey查询Map对象
     *
     * @param query        查询参数
     * @param mapKey       Map的key
     * @param tableColumns 指定表的字段
     * @return 返回Map对象
     */
    public <E> Map<E, T> findMap(Query query, String mapKey, String... tableColumns) {
        if (tableColumns == null || tableColumns.length == 0) {
            tableColumns = this.tableColumns;
        } else {
            List<String> tableColumnList = Utilities.asList(tableColumns);
            if (!tableColumnList.contains(mapKey)) {
                tableColumnList.add(mapKey);
                tableColumns = Utilities.toArray(tableColumnList);
            }
        }
        return sqlSessionTemplate.selectMap(statement.listStatement(), query.toMap(this.tableName, Utilities.asList(tableColumns)), this.columnPropertyMap.get(mapKey));
    }

    /**
     * 根据参数和mapKey查询Map对象
     *
     * @param query        查询参数
     * @param mapKey       Map的key
     * @param tableColumns 指定表的字段
     * @return 返回Map对象
     */
    @SuppressWarnings("unchecked")
    public <E> Map<E, Entity> findEntityMap(Query query, String mapKey, String... tableColumns) {
        if (tableColumns == null || tableColumns.length == 0) {
            tableColumns = this.tableColumns;
        } else {
            List<String> tableColumnList = Utilities.asList(tableColumns);
            if (!tableColumnList.contains(mapKey)) {
                tableColumnList.add(mapKey);
                tableColumns = Utilities.toArray(tableColumnList);
            }
        }
        List<Entity> entities = findEntityList(query, tableColumns);
        Map<E, Entity> entityMap = new HashMap<E, Entity>();
        for (Entity entity : entities) {
            entityMap.put((E) (entity.get(mapKey)), entity);
        }
        return entityMap;
    }

    public <C> C getColumn(Query query, String tableColumn) {
        List<C> columnVals = findColumn(query, tableColumn, true, true);
        return Utilities.isNotEmpty(columnVals) ? columnVals.get(0) : null;
    }

    /**
     * 获取指定字段的值
     *
     * @param query       查询参数
     * @param tableColumn 指定表的字段
     * @param filterEmpty 是否过滤空值
     * @param <C>         字段值类型
     * @return 返回字段值列表
     */
    @SuppressWarnings("unchecked")
    public <C> List<C> findColumn(Query query, String tableColumn, boolean filterEmpty, boolean unique) {
        List<Entity> list = findEntityList(query, tableColumn);
        List<C> columnVals = new ArrayList<C>();
        if (list != null) {
            for (Entity entity : list) {
                if (filterEmpty && entity.get(tableColumn) == null) {
                    continue;
                }
                columnVals.add((C) (entity.get(tableColumn)));
            }
        }
        if (unique && columnVals.size() > 0) {
            columnVals = (ArrayList<C>) Utilities.unique(columnVals);
        }
        return columnVals;
    }

    /**
     * 查询记录数
     *
     * @param query 查询参数
     * @return 返回数量
     */
    public Long count(Query query) {
        return fx(query.copy().start(null).limit(null), Fx.COUNT, idColumn);
    }

    /**
     * 对指定字段求和
     *
     * @param query  查询参数
     * @param column 求和字段
     * @return 返回求和值
     */
    public Object sum(Query query, String column) {
        return fx(query, Fx.SUM, column);
    }

    /**
     * 获取指定字段的最大值
     *
     * @param query  查询参数
     * @param column 求最大值字段
     * @return 返回最大值
     */
    public Object max(Query query, String column) {
        return fx(query, Fx.MAX, column);
    }

    /**
     * 获取指定字段的最小值
     *
     * @param query  查询参数
     * @param column 求最小值字段
     * @return 返回最小值
     */
    public Object min(Query query, String column) {
        return fx(query, Fx.MIN, column);
    }

    /**
     * 获取指定字段的平均值
     *
     * @param query  查询参数
     * @param column 求平均值字段
     * @return 返回平均值
     */
    public Object avg(Query query, String column) {
        return fx(query, Fx.AVG, column);
    }

    /**
     * 执行函数查询
     *
     * @param query  查询参数
     * @param fx     函数名
     * @param column 函数字段
     * @param <E>    返回类型
     * @return 返回函数查询结果
     */
    @SuppressWarnings("unchecked")
    public <E> E fx(Query query, Fx fx, String column) {
        Map<String, Object> map = getEntity(query, Column.fx(fx, column, column));
        if (map != null) {
            Object val = map.get(column);
            if (val != null) {
                return (E) val;
            }
        }
        return null;
    }

    /**
     * 创建表记录
     * 如果主键是autoIncrement，则执行完成后entity的主键会被填充自增长值
     * 如果主键不是autoIncrement，则id不能为空
     *
     * @param entity 实体对象
     */
    public void create(T entity) {
        if (entity == null) {
            throw new MybatisDAOCheckedException("entity can't be null");
        }
        if (!this.autoIncrement && getEntityPropertyValue(entity, idProperty) == null) {
            throw new MybatisDAOCheckedException("entity'id can't be null");
        }
        Map<String, Object> map = new HashMap<String, Object>();
        map.put(MybatisContext.TABLE_NAME, this.tableName);
        map.put(MybatisContext.ENTITY, entity);
        sqlSessionTemplate.insert(statement.insertStatement(), map);
    }

    /**
     * 批量创建表记录，不会填充entity的主键
     * 如果主键不是autoIncrement，则所有的entities的id不能为空
     *
     * @param entities 实体对象数组
     */
    public void createBatch(T... entities) {
        createBatch(Utilities.asList(entities));
    }

    /**
     * 批量创建表记录，不会填充entity的主键
     * 如果主键不是autoIncrement，则所有的entities的id不能为空
     *
     * @param entities 实体对象数组
     */
    public void createBatch(Collection<T> entities) {
        if (entities == null || entities.size() == 0) {
            throw new MybatisDAOCheckedException("entities can't be empty");
        }
        for (T entity : entities) {
            if (!this.autoIncrement && getEntityPropertyValue(entity, idProperty) == null) {
                throw new MybatisDAOCheckedException("entity'id can't be null");
            }
        }
        Map<String, Object> map = new HashMap<String, Object>();
        map.put(MybatisContext.TABLE_NAME, this.tableName);
        map.put(MybatisContext.ENTITIES, entities);
        sqlSessionTemplate.insert(statement.insertBatchStatement(), map);
    }

    /**
     * 更新表记录
     *
     * @param entity 实体对象
     */
    public void update(T entity) {
        update(entity, false);
    }

    /**
     * 更新表记录
     *
     * @param entity        实体对象
     * @param withNullField 是否更新值为null的字段
     */
    public void update(T entity, boolean withNullField) {
        update(entity, Utilities.asList(this.tableColumns), withNullField);
    }

    /**
     * 更新表记录
     *
     * @param entity        实体对象
     * @param updateColumns 更新指定的字段
     */
    public void update(T entity, List<String> updateColumns) {
        update(entity, updateColumns, false);
    }

    /**
     * 更新表记录
     *
     * @param entity        实体对象
     * @param updateColumns 更新指定的字段
     * @param withNullField 是否更新指定字段中值为null的字段
     */
    public void update(T entity, List<String> updateColumns, boolean withNullField) {
        P idPropertyVal = getEntityPropertyValue(entity, idProperty);
        Updater updater = Updater.fromEntity(entity, updateColumns, withNullField, idColumn, columnPropertyMap);
        update(idPropertyVal, updater);
    }

    /**
     * 通过主键更新指定字段
     *
     * @param id      主键编号
     * @param updater 字段更新参数
     */
    public void update(P id, Updater updater) {
        if (id == null) {
            throw new MybatisDAOCheckedException("id can't be null");
        }
        update(Query.query(idProperty, id), updater);
    }

    /**
     * 通过主键更新指定字段
     *
     * @param ids     主键编号
     * @param updater 字段更新参数
     */
    public void update(Collection<P> ids, Updater updater) {
        if (Utilities.isEmpty(ids)) {
            throw new MybatisDAOCheckedException("ids can't be empty");
        }
        update(Query.inQuery(idProperty, ids), updater);
    }

    /**
     * 通过查询参数更新指定字段
     *
     * @param query   查询参数
     * @param updater 字段更新参数
     */
    public void update(Query query, Updater updater) {
        if (updater.getFields().size() == 0) {
            throw new MybatisDAOCheckedException("updater'fields can't be empty");
        }
        sqlSessionTemplate.update(statement.updateStatement(), updater.toMap(this.tableName, query));
    }

    /**
     * 根据主键删除表记录
     *
     * @param ids 主键编号
     */
    public void delete(P... ids) {
        delete(Utilities.asList(ids));
    }

    /**
     * 根据主键删除表记录
     *
     * @param ids 主键编号
     */
    public void delete(Collection<P> ids) {
        if (Utilities.isEmpty(ids)) {
            throw new MybatisDAOCheckedException("ids can't be empty");
        }
        delete(Query.inQuery(idColumn, ids));
    }

    /**
     * 根据查询参数删除表记录
     *
     * @param query 查询参数
     */
    public void delete(Query query) {
        sqlSessionTemplate.delete(statement.deleteStatement(), query.toMap(this.tableName));
    }
}
